﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml;

namespace Elderos.Utils.Serialization
{
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class |
					AttributeTargets.Interface)]
	public class NetDataContractFormatAttribute : Attribute, IOperationBehavior,
		IServiceBehavior, IContractBehavior
	{
		#region IContractBehavior Members

		void IContractBehavior.AddBindingParameters(
			ContractDescription contractDescription,
			ServiceEndpoint endpoint,
			BindingParameterCollection bindingParameters) {}


		void IContractBehavior.ApplyClientBehavior(
			ContractDescription contractDescription,
			ServiceEndpoint endpoint,
			ClientRuntime clientRuntime)
		{
			ReplaceDataContractSerializerOperationBehavior(contractDescription);
		}


		void IContractBehavior.ApplyDispatchBehavior(
			ContractDescription contractDescription,
			ServiceEndpoint endpoint,
			DispatchRuntime dispatchRuntime)
		{
			ReplaceDataContractSerializerOperationBehavior(contractDescription);
		}


		void IContractBehavior.Validate(
			ContractDescription contractDescription,
			ServiceEndpoint endpoint) {}

		#endregion

		#region IOperationBehavior Members

		void IOperationBehavior.AddBindingParameters(
			OperationDescription description, BindingParameterCollection parameters) {}


		void IOperationBehavior.ApplyClientBehavior(
			OperationDescription description,
			ClientOperation proxy)
		{
			ReplaceDataContractSerializerOperationBehavior(description);
		}

		void IOperationBehavior.ApplyDispatchBehavior(
			OperationDescription description,
			DispatchOperation dispatch)
		{
			ReplaceDataContractSerializerOperationBehavior(description);
		}

		void IOperationBehavior.Validate(
			OperationDescription description) {}

		#endregion

		#region IServiceBehavior Members

		void IServiceBehavior.AddBindingParameters(
			ServiceDescription serviceDescription,
			ServiceHostBase serviceHostBase,
			Collection<ServiceEndpoint> endpoints,
			BindingParameterCollection bindingParameters)
		{
			ReplaceDataContractSerializerOperationBehavior(serviceDescription);
		}


		void IServiceBehavior.ApplyDispatchBehavior(
			ServiceDescription serviceDescription,
			ServiceHostBase serviceHostBase)
		{
			ReplaceDataContractSerializerOperationBehavior(serviceDescription);
		}


		void IServiceBehavior.Validate(
			ServiceDescription serviceDescription,
			ServiceHostBase serviceHostBase) {}

		#endregion

		private static void ReplaceDataContractSerializerOperationBehavior(
			ServiceDescription description)
		{
			foreach (ServiceEndpoint endpoint in description.Endpoints)
				ReplaceDataContractSerializerOperationBehavior(endpoint);
		}


		private static void ReplaceDataContractSerializerOperationBehavior(
			ContractDescription description)
		{
			foreach (OperationDescription operation in description.Operations)
				ReplaceDataContractSerializerOperationBehavior(operation);
		}


		private static void ReplaceDataContractSerializerOperationBehavior(
			ServiceEndpoint endpoint)
		{
			// ignore mex

			if (endpoint.Contract.ContractType == typeof(IMetadataExchange))
				return;

			ReplaceDataContractSerializerOperationBehavior(endpoint.Contract);
		}


		private static void ReplaceDataContractSerializerOperationBehavior(
			OperationDescription description)
		{
			var behavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();

			if (behavior != null)
			{
				description.Behaviors.Remove(behavior);

				description.Behaviors.Add(new NetDataContractSerializerOperationBehavior(description));
			}
		}

		#region Nested type: NetDataContractSerializerOperationBehavior

		public class NetDataContractSerializerOperationBehavior
			: DataContractSerializerOperationBehavior
		{
			public NetDataContractSerializerOperationBehavior(
				OperationDescription description)
				: base(description) {}


			public override XmlObjectSerializer CreateSerializer(
				Type type, string name,
				string ns, IList<Type> knownTypes)
			{
				return new NetDataContractSerializer();
			}


			public override XmlObjectSerializer CreateSerializer(
				Type type, XmlDictionaryString name,
				XmlDictionaryString ns, IList<Type> knownTypes)
			{
				return new NetDataContractSerializer();
			}
		}

		#endregion
	}
}